#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include "fb.h"
#include "opt.h"

static int calc_fb_size(int xsz, int ysz, int nsamples);
static void init_framebuffer(struct framebuffer *fb, int xsz, int ysz, int nsamples);

static int manage_shm;

/** Allocates memory for the framebuffer.
 *
 * If a shared memory segment is to be used, it's opened or created and mapped.
 * Otherwise, the framebuffer is allocated through malloc.
 */
struct framebuffer *alloc_framebuffer(void)
{
	struct framebuffer *fb;
	int fbsize = calc_fb_size(opt.width, opt.height, opt.max_samples);

	if(opt.shm) {
		int fd;

		if((fd = shm_open(opt.shm, O_RDWR, 0)) == -1) {
			if(errno != ENOENT) {
				fprintf(stderr, "failed to open shared memory: %s: %s\n", opt.shm, strerror(errno));
				return 0;
			} else if((fd = shm_open(opt.shm, O_RDWR | O_CREAT, 0644)) == -1) {
				fprintf(stderr, "failed to create shared memory: %s: %s\n", opt.shm, strerror(errno));
				return 0;
			}
			manage_shm = 1;
		}
		if(ftruncate(fd, fbsize) == -1) {
			perror("failed to set shared memory size");
			if(manage_shm) {
				shm_unlink(opt.shm);
			}
			return 0;
		}
		if((fb = mmap(0, fbsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == (void*)-1) {
			fprintf(stderr, "failed to map shared memory segment: %s: %s\n", opt.shm, strerror(errno));
			if(manage_shm) {
				shm_unlink(opt.shm);
			}
			return 0;
		}
		close(fd);

	} else {
		if(!(fb = malloc(fbsize))) {
			perror("failed to allocate framebuffer");
			return 0;
		}
	}

	init_framebuffer(fb, opt.width, opt.height, opt.max_samples);
	return fb;
}

/** Frees the framebuffer allocated with alloc_framebuffer. */
void free_framebuffer(struct framebuffer *fb)
{
	if(opt.shm) {
		munmap(fb, calc_fb_size(opt.width, opt.height, opt.max_samples));
		if(manage_shm) {
			shm_unlink(opt.shm);
		}
	} else {
		free(fb);
	}
}

static int calc_fb_size(int xsz, int ysz, int nsamples)
{
	int npixels = xsz * ysz;

	return sizeof(struct framebuffer) + (npixels - 1) * sizeof(struct pixel) +
		npixels * nsamples * sizeof(struct subpixel);
}

/** Initialize the framebuffer.
 *
 * Initializes all the framebuffer structures, and points all the subpixel
 * pointers to the correct addresses within the framebuffer memory block.
 */
static void init_framebuffer(struct framebuffer *fb, int xsz, int ysz, int nsamples)
{
	int i, npixels;
	struct subpixel *sp;

	fb->width = xsz;
	fb->height = ysz;

	npixels = xsz * ysz;
	/* get pointer to the beginning of the subpixel array */
	sp = (struct subpixel*)((char*)fb + sizeof(*fb) + (npixels - 1) * sizeof(struct pixel));

	for(i=0; i<npixels; i++) {
		fb->pix[i].r = fb->pix[i].g = fb->pix[i].b = 0.0f;
		fb->pix[i].num_subpix = 0;
		fb->pix[i].subpix = sp + i * nsamples;
	}

	/* sanity check */
	{
		int fbsz = calc_fb_size(xsz, ysz, nsamples);
		void *end = fb->pix[npixels - 1].subpix + nsamples;
		assert(end == (char*)fb + fbsz);
	}
}
